package com.fzebu.fastboot.crypto;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;

import static com.fzebu.fastboot.uuid.UuidUtil.CHARS;
import static com.fzebu.fastboot.uuid.UuidUtil.CHARS_LENGTH;


/**
 * @author Y-Aron
 * @version 1.0.0
 * @create 2019/6/13 14:25
 */
public class MD5Utils {
    /**
     * 十六进制
     */
    public static final char[] HEX = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
            '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    /**
     * 默认加密次数
     */
    private static final int DEFAULT_ENCIPHER_SIZE = 1;

    /**
     * 盐值长度
     */
    private static final int SALT_SIZE = 16;

    /**
     * 加盐后密文的长度
     */
    private static final int SALT_MD5_SIZE = 48;

    private static final int STEP = 3;

    private static final ThreadLocal<MessageDigest> THREAD_LOCAL = new ThreadLocal<>();

    private static final String MD5 = "MD5";

    /**
     * md5加盐一次加密
     * @param text 文本值
     * @param salt 盐值
     */
    public static String md5Hex(String text, Object salt) {
        return md5Hex(text, salt, DEFAULT_ENCIPHER_SIZE);
    }

    /**
     * md5加盐多次加密
     * 当盐值为空时，使用text作为加密盐
     * @param text 需要加密的文本
     * @param salt 盐值
     * @param size 加密次数
     */
    public static String md5Hex(String text, Object salt, int size) {
        if (size == 0 || text == null) {
            return text;
        }
        if (salt == null) {
            salt = text;
        }
        char[] array = Integer.toString(salt.hashCode()).toCharArray();
        char[] chars = new char[SALT_SIZE];
        Random random = new Random();
        for (int i = 0, total = array.length; i < SALT_SIZE; i++) {
            if (i < total) {
                chars[i] = array[i];
            } else {
                chars[i] = CHARS[random.nextInt(CHARS_LENGTH)];
            }
        }
        // 生成最终的加密盐
        String saltStr = new String(chars);
        text = md5Hex(text + saltStr, size);
        char[] out = new char[SALT_MD5_SIZE];
        char[] textChars = text.toCharArray();
        char[] saltChars = saltStr.toCharArray();
        for (int i = 0; i < SALT_MD5_SIZE; i += STEP) {
            out[i] = textChars[i / 3 * 2];
            out[i + 1] = saltChars[i /3];
            out[i + 2] = textChars[i / 3 * 2 + 1];
        }
        return new String(out);
    }

    public static String md5Hex(String text) {
        if (text == null) {
            return null;
        }
        return md5Hex(text.getBytes(StandardCharsets.UTF_8));
    }

    public static String md5Hex(String text, int size) {
        if (text == null || size == 0) {
            return null;
        }
        for (int i = 0; i < size; i++) {
            text = md5Hex(text);
        }
        return text;
    }

    public static boolean equals(String text, String cipherText) {
        return equals(text, cipherText, DEFAULT_ENCIPHER_SIZE);
    }

    public static boolean equals(String text, String cipherText, int size) {
        if (text == null && cipherText == null) {
            return true;
        }
        if (cipherText.length() < SALT_MD5_SIZE) {
            return false;
        }
        char[] src = new char[32];
        char[] saltChars = new char[SALT_SIZE];
        char[] chars = cipherText.toCharArray();
        for (int i = 0; i < SALT_MD5_SIZE; i+= STEP) {
            src[i / 3 * 2] = chars[i];
            src[i / 3 * 2 + 1] = chars[i + 2];
            saltChars[i / 3] = chars[i + 1];
        }
        String salt = new String(saltChars);
        return md5Hex(text + salt, size).equals(new String(src));
    }

    public static String md5Hex(byte[] bytes) {
        MessageDigest messageDigest = getMessageDigest();
        messageDigest.update(bytes);
        return md5Hex(messageDigest);
    }

    /**
     * 获取文件的md5
     * @param file 文件
     */
    public static String md5Hex(File file) throws IOException {
        if (!file.exists()) {
            return null;
        }
        FileInputStream fis = new FileInputStream(file);
        FileChannel channel = fis.getChannel();
        MessageDigest messagedigest = getMessageDigest();
        long position = 0;
        long remaining = channel.size();
        MappedByteBuffer byteBuffer = null;
        while (remaining > 0) {
            long size = Integer.MAX_VALUE / 2;
            if (size > remaining) {
                size = remaining;
            }
            byteBuffer = channel.map(FileChannel.MapMode.READ_ONLY, position, size);
            messagedigest.update(byteBuffer);
            position += size;
            remaining -= size;
        }
        if (byteBuffer != null) {
            byteBuffer.clear();
        }
        return md5Hex(messagedigest);
    }

    private static String md5Hex(MessageDigest messageDigest) {
        byte[] data = messageDigest.digest();
        // 获得密文
        int l = data.length;
        char[] out = new char[l << 1];
        for(int j = 0, i = 0; i < l; ++i) {
            out[j++] = HEX[(240 & data[i]) >>> 4];
            out[j++] = HEX[15 & data[i]];
        }
        return new String(out);
    }

    private static MessageDigest getMessageDigest() {
        MessageDigest messageDigest = THREAD_LOCAL.get();
        if (messageDigest == null) {
            try {
                messageDigest = MessageDigest.getInstance(MD5);
                THREAD_LOCAL.set(messageDigest);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
                THREAD_LOCAL.remove();
            }
        }
        return messageDigest;
    }
}